﻿#region MIT License

/*
 * Copyright (c) 2009 Nick Gravelyn (nick@gravelyn.com), Markus Ewald (cygon@nuclex.org)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the Software 
 * is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all 
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 * 
 */

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using AnimationPackerEngine;
using AnimationPackerEngine.Exporters;

namespace AnimatedSpritePacker
{
    internal partial class SpriteSheetPackerForm : Form
    {
        private string mExeDir;
        private string mOrigWorkingDir;
        private ProgressForm mProgress = new ProgressForm();

        private ProcessingAnimationCollection mAnimations;

        private class PackingStatus : IPackingStatus
        {
            private TextBox mTextBox;

            public PackingStatus(TextBox textBox)
            {
                mTextBox = textBox;
            }

            public void FatalException(string message, Exception e)
            {
                Fatal(message + ": " + MiscHelper.GetFullExceptionMessage(e));
            }

            public void Fatal(string message)
            {
                mTextBox.Invoke(new MethodInvoker(() =>
                {
                    List<string> lines = new List<string>(mTextBox.Lines);
                    lines.Add(message);
                    mTextBox.Lines = lines.ToArray();
                }));
            }

            public void Error(string message)
            {
                mTextBox.Invoke(new MethodInvoker(() =>
                {
                    List<string> lines = new List<string>(mTextBox.Lines);
                    lines.Add(message);
                    mTextBox.Lines = lines.ToArray();
                }));
            }

            public void Warning(string message)
            {
                mTextBox.Invoke(new MethodInvoker(() =>
                {
                    List<string> lines = new List<string>(mTextBox.Lines);
                    lines.Add(message);
                    mTextBox.Lines = lines.ToArray();
                }));
            }

            public void Information(string message)
            {
                mTextBox.Invoke(new MethodInvoker(() =>
                {
                    List<string> lines = new List<string>(mTextBox.Lines);
                    lines.Add(message);
                    mTextBox.Lines = lines.ToArray();
                }));
            }

            public void Debug(string message)
            {
                mTextBox.Invoke(new MethodInvoker(() =>
                {
                    List<string> lines = new List<string>(mTextBox.Lines);
                    lines.Add(message);
                    mTextBox.Lines = lines.ToArray();
                }));
            }
        }

        PackingStatus mPackingStatus;

        public SpriteSheetPackerForm()
        {
            InitializeComponent();

            mPackingStatus = new PackingStatus(mCommandLineView);

            // for calculating commandline params
            mExeDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
            mOrigWorkingDir = Directory.GetCurrentDirectory();

            // find all of the available exporters
            ExporterCollection.Load();

            // generate the save dialogs based on available exporters
            GenerateImageSaveDialog();
            GenerateMapSaveDialog();
            GenerateIndexSaveDialog();

            // set the UI values to our saved settings
            maxWidthTxtBox.Text = AnimatedSpritePacker.Settings.Default.MaxWidth.ToString();
            maxHeightTxtBox.Text = AnimatedSpritePacker.Settings.Default.MaxHeight.ToString();
            paddingTxtBox.Text = AnimatedSpritePacker.Settings.Default.Padding.ToString();
            powOf2CheckBox.Checked = AnimatedSpritePacker.Settings.Default.PowOf2;
            squareCheckBox.Checked = AnimatedSpritePacker.Settings.Default.Square;
        }

        // configures our image save dialog to take into account all loaded image exporters
        public void GenerateImageSaveDialog()
        {
            string filter = "";
            int filterIndex = 0;
            int i = 0;

            foreach (var exporter in ExporterCollection.ImageExporters)
            {
                i++;
                filter += string.Format("{0} Files|*.{1}|", exporter.ImageExtension.ToUpper(), exporter.ImageExtension);
                if (exporter.ImageExtension.ToLower() == AnimatedSpritePacker.Settings.Default.ImageExt)
                {
                    filterIndex = i;
                }
            }

            filter = filter.Remove(filter.Length - 1);

            imageSaveFileDialog.Filter = filter;
            imageSaveFileDialog.FilterIndex = filterIndex;
        }

        public void GenerateMapSaveDialog()
        {
            string filter = "";
            int filterIndex = 0;
            int i = 0;

            foreach (var exporter in ExporterCollection.MapExporters)
            {
                i++;
                filter += string.Format("{0} Files|*.{1}|", exporter.MapExtension.ToUpper(), exporter.MapExtension);
                if (exporter.MapExtension.ToLower() == AnimatedSpritePacker.Settings.Default.MapExt)
                {
                    filterIndex = i;
                }
            }

            filter = filter.Remove(filter.Length - 1);

            mapSaveFileDialog.Filter = filter;
            mapSaveFileDialog.FilterIndex = filterIndex;
        }

        public void GenerateIndexSaveDialog()
        {
            string filter = "";
            int filterIndex = 0;
            int i = 0;

            foreach (var exporter in ExporterCollection.IndexExporters)
            {
                i++;
                filter += string.Format("{0} Files|*.{1}|", exporter.IndexExtension.ToUpper(), exporter.IndexExtension);
                if (exporter.IndexExtension.ToLower() == Settings.Default.IndexExt)
                {
                    filterIndex = i;
                }
            }

            filter = filter.Remove(filter.Length - 1);

            indexSaveFileDialog.Filter = filter;
            indexSaveFileDialog.FilterIndex = filterIndex;
        }

        private void SetRootAnimationDirectory(string path)
        {
            UseWaitCursor = true;
            Enabled = false;

            mAnimationList.Items.Clear();
            mCommandLineView.Clear();

            mAnimations = Worker.GetAnimationsFromDirectory(path, mPackingStatus);

            foreach (ProcessingAnimation animation in mAnimations.AnimationCollection)
            {
                mAnimationList.Items.Add(animation.Name);
                mAnimationList.Items.Add("    " + animation.Frames.Count + " images");
                mAnimationList.Items.Add("    " + animation.AnimationCompiler.Animations.Count + " animations");
            }

            if (mAnimations.AnimationCollection.Count > 0)
            {
                Settings.Default.LastAnimationDirectory = path;
                Settings.Default.Save();

                Directory.SetCurrentDirectory(path);
            }

            ComponentModified(this, new EventArgs());

            Enabled = true;
            UseWaitCursor = false;
        }

        private void SetAnimationsRoot_Click(object sender, EventArgs e)
        {
            parentFolderBrowseDialog.SelectedPath = Directory.GetCurrentDirectory();
            if (Directory.Exists(Settings.Default.LastAnimationDirectory))
            {
                parentFolderBrowseDialog.SelectedPath = Settings.Default.LastAnimationDirectory;
            }

            // show our file dialog and add all the resulting files
            if (parentFolderBrowseDialog.ShowDialog() == DialogResult.OK)
            {
                SetRootAnimationDirectory(parentFolderBrowseDialog.SelectedPath);
            }
        }

        private void SaveImageBrowseBtn_Click(object sender, EventArgs e)
        {
            imageSaveFileDialog.InitialDirectory = Directory.GetCurrentDirectory();
            if (Directory.Exists(Settings.Default.LastImageDirectory))
            {
                imageSaveFileDialog.InitialDirectory = Settings.Default.LastImageDirectory;
                imageSaveFileDialog.FileName = Settings.Default.LastImageFile;
            }

            if (imageSaveFileDialog.ShowDialog() == DialogResult.OK)
            {
                // store the image path
                imageFileTxtBox.Text = imageSaveFileDialog.FileName;

                Settings.Default.LastImageDirectory = Path.GetDirectoryName(imageFileTxtBox.Text);
                Settings.Default.LastImageFile = Path.GetFileName(imageFileTxtBox.Text);
                Settings.Default.ImageExt = Path.GetExtension(imageSaveFileDialog.FileName).Substring(1);
                Settings.Default.Save();

                Directory.SetCurrentDirectory(Settings.Default.LastImageDirectory);
            }
        }

        private void SaveMapBrowseBtn_Click(object sender, EventArgs e)
        {
            mapSaveFileDialog.InitialDirectory = Directory.GetCurrentDirectory();
            if (Directory.Exists(Settings.Default.LastMapDirectory))
            {
                mapSaveFileDialog.InitialDirectory = Settings.Default.LastMapDirectory;
                mapSaveFileDialog.FileName = Settings.Default.LastMapFile;
            }

            if (mapSaveFileDialog.ShowDialog() == DialogResult.OK)
            {
                // store the image path
                mapFileTxtBox.Text = mapSaveFileDialog.FileName;

                Settings.Default.LastMapDirectory = Path.GetDirectoryName(mapFileTxtBox.Text);
                Settings.Default.LastMapFile = Path.GetFileName(mapFileTxtBox.Text);
                Settings.Default.MapExt = Path.GetExtension(mapSaveFileDialog.FileName).Substring(1);
                Settings.Default.Save();

                Directory.SetCurrentDirectory(Settings.Default.LastMapDirectory);
            }
        }

        private void SaveIndexBrowseBtn_Click(object sender, EventArgs e)
        {
            indexSaveFileDialog.InitialDirectory = Directory.GetCurrentDirectory();
            if (Directory.Exists(Settings.Default.LastIndexDirectory))
            {
                indexSaveFileDialog.InitialDirectory = Settings.Default.LastIndexDirectory;
                indexSaveFileDialog.FileName = Settings.Default.LastIndexFile;
            }

            if (indexSaveFileDialog.ShowDialog() == DialogResult.OK)
            {
                // store the image path
                indexFileTxtBox.Text = indexSaveFileDialog.FileName;

                Settings.Default.LastIndexDirectory = Path.GetDirectoryName(indexFileTxtBox.Text);
                Settings.Default.LastIndexFile = Path.GetFileName(indexFileTxtBox.Text);
                Settings.Default.IndexExt = Path.GetExtension(indexSaveFileDialog.FileName).Substring(1);
                Settings.Default.Save();

                Directory.SetCurrentDirectory(Settings.Default.LastIndexDirectory);
            }
        }

        private void buildBtn_Click(object sender, EventArgs e)
        {
            // check our parameters
            if (mAnimations.AnimationCollection.Count == 0)
            {
                mPackingStatus.Error("No images to pack into sheet");
                return;
            }
            if (string.IsNullOrEmpty(imageFileTxtBox.Text))
            {
                mPackingStatus.Error("No image filename given.");
                return;
            }

            int outputWidth, outputHeight, padding;
            if (!int.TryParse(maxWidthTxtBox.Text, out outputWidth) || outputWidth < 1)
            {
                mPackingStatus.Error("Maximum width is not a valid integer value greater than 0.");
                return;
            }
            if (!int.TryParse(maxHeightTxtBox.Text, out outputHeight) || outputHeight < 1)
            {
                mPackingStatus.Error("Maximum height is not a valid integer value greater than 0.");
                return;
            }
            if (!int.TryParse(paddingTxtBox.Text, out padding) || padding < 0)
            {
                mPackingStatus.Error("Image padding is not a valid non-negative integer");
                return;
            }

            Enabled = false;
            UseWaitCursor = true;

            mCommandLineView.Clear();

            mProgress.Show();
            mProgress.ResetProgress(1);

            ProcessParams compileParams = new ProcessParams();
            compileParams.MaxWidth = int.Parse(maxWidthTxtBox.Text);
            compileParams.MaxHeight = int.Parse(maxHeightTxtBox.Text);
            compileParams.Padding = int.Parse(paddingTxtBox.Text);
            compileParams.Pow2 = powOf2CheckBox.Checked;
            compileParams.Square = squareCheckBox.Checked;
            compileParams.SpriteAtlasImage = imageFileTxtBox.Text;
            compileParams.SpriteAtlasMap = mapFileTxtBox.Text;
            compileParams.SpriteAtlasIndex = indexFileTxtBox.Text;

            Thread workerThread = new Thread(() =>
                {
                    Worker.ProcessAnimations(mAnimations, compileParams, mProgress, mPackingStatus);
                    this.Invoke(new Action(BuildThreadComplete));
                })
                {
                    IsBackground = true,
                    Name = "Packer Worker Thread",
                };

            workerThread.Start();
        }

        private void BuildThreadComplete()
        {
            mProgress.Hide();
            Show();

            List<string> lines = new List<string>(mCommandLineView.Lines);
            lines.Add("Build complete.");
            mCommandLineView.Lines = lines.ToArray();

            UseWaitCursor = false;
            Enabled = true;
        }

        protected override void OnClosed(EventArgs e)
        {
            // get our UI values if they are valid
            int outputWidth, outputHeight, padding;

            if (int.TryParse(maxWidthTxtBox.Text, out outputWidth) && outputWidth > 0)
                AnimatedSpritePacker.Settings.Default.MaxWidth = outputWidth;

            if (int.TryParse(maxHeightTxtBox.Text, out outputHeight) && outputHeight > 0)
                AnimatedSpritePacker.Settings.Default.MaxHeight = outputHeight;

            if (int.TryParse(paddingTxtBox.Text, out padding) && padding >= 0)
                AnimatedSpritePacker.Settings.Default.Padding = padding;

            AnimatedSpritePacker.Settings.Default.PowOf2 = powOf2CheckBox.Checked;
            AnimatedSpritePacker.Settings.Default.Square = squareCheckBox.Checked;

            // save the settings
            AnimatedSpritePacker.Settings.Default.Save();
        }

        private void mClearImageBtn_Click(object sender, EventArgs e)
        {
            imageFileTxtBox.Text = "";
        }

        private void mClearMapBtn_Click(object sender, EventArgs e)
        {
            mapFileTxtBox.Text = "";
        }

        private void mClearIndexBtn_Click(object sender, EventArgs e)
        {
            indexFileTxtBox.Text = "";
        }

        private string BuildCommandlineView()
        {
            string output = "";

            if (mAnimations != null && mAnimations.AnimationCollection.Count > 0)
            {
                output = PathExt.RelativePathTo(mOrigWorkingDir, Path.Combine(mExeDir, "sap.exe"));

                if (!string.IsNullOrEmpty(imageFileTxtBox.Text))
                {
                    output += " /i:\"" + PathExt.RelativePathTo(mOrigWorkingDir, imageFileTxtBox.Text) + "\"";
                }

                if (!string.IsNullOrEmpty(mapFileTxtBox.Text))
                {
                    output += " /m:\"" + PathExt.RelativePathTo(mOrigWorkingDir, mapFileTxtBox.Text) + "\"";
                }

                if (!string.IsNullOrEmpty(indexFileTxtBox.Text))
                {
                    output += " /idx:\"" + PathExt.RelativePathTo(mOrigWorkingDir, indexFileTxtBox.Text) + "\"";
                }

                int outputWidth, outputHeight, padding;
                if (int.TryParse(maxWidthTxtBox.Text, out outputWidth) && outputWidth > 0)
                {
                    output += " /mw:" + maxWidthTxtBox.Text;
                }
                if (int.TryParse(maxHeightTxtBox.Text, out outputHeight) && outputHeight > 0)
                {
                    output += " /mh:" + maxHeightTxtBox.Text;
                }
                if (int.TryParse(paddingTxtBox.Text, out padding) && padding >= 0)
                {
                    output += " /p:" + paddingTxtBox.Text;
                }

                if (powOf2CheckBox.Checked)
                {
                    output += " /p2";
                }

                if (squareCheckBox.Checked)
                {
                    output += " /s";
                }

                output += " \"" + PathExt.RelativePathTo(mOrigWorkingDir, Settings.Default.LastAnimationDirectory) + "\"";
            }

            return output;
        }

        private void ComponentModified(object sender, EventArgs e)
        {
            mCommandLineView.Text = BuildCommandlineView();
        }
    }
}
